/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package smartcardjava;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.List;
import javax.smartcardio.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import static smartcardjava.SmartCardTools.calculateHash;
import static smartcardjava.SmartCardTools.concatenateTwoByteArray;
import static smartcardjava.SmartCardTools.fromByteArrayToX509Certificate;
import static smartcardjava.SmartCardTools.fromBytesToString;
import static smartcardjava.SmartCardTools.hexStringToByteArray;

/**
 *
 * @author rmartinezch
 */
public class SmartCardJava {
    
    /**
     * @param args the command line arguments
     */
    final static byte[] select      = { (byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02};
    final static byte[] select_aid  = { (byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte) 0x10};
    final static byte[] verify      = { (byte) 0x00, (byte) 0x20, (byte) 0x00};
    final static byte[] mse         = { (byte) 0x00, (byte) 0x22, (byte) 0x41, (byte) 0xB6, (byte) 0x06, (byte) 0x80, (byte) 0x01, (byte) 0x11, (byte) 0x83, (byte) 0x01};
    
    final static byte[] pso_sha1    = { (byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x21,
        (byte) 0x30, (byte) 0x1F,
        (byte) 0x30, (byte) 0x07,
        // oid sha1
        (byte) 0x06, (byte) 0x05, (byte) 0x2B, (byte) 0x0E, (byte) 0x03, (byte) 0x02, (byte) 0x1A,
        // tl-v
        (byte) 0x04, (byte) 0x14};
    
    final static byte[] pso_sha224  = { (byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x2D,
        (byte) 0x30, (byte) 0x2B,
        (byte) 0x30, (byte) 0x0B,
        // id-sha224: 30 0b 06 09 60 86 48 01 65 03 04 02 04
        (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0x04,
        // tl-v
        (byte) 0x04, (byte) 0x1C};//1C=224/8
    
    final static byte[] pso_sha256  = { (byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x31,
        (byte) 0x30, (byte) 0x2F,
        (byte) 0x30, (byte) 0x0B,
        // id-sha256: 30 0b 06 09 60 86 48 01 65 03 04 02 01
        (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0x01,
        // tl-v
        (byte) 0x04, (byte) 0x20};

    final static byte[] pso_sha384  = { (byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x41,
        (byte) 0x30, (byte) 0x3F,
        (byte) 0x30, (byte) 0x0B,
        // id-sha384: 30 0b 06 09 60 86 48 01 65 03 04 02 02
        (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0x02,
        // tl-v
        (byte) 0x04, (byte) 0x30};//0x30=384/8

    final static byte[] pso_sha512  = { (byte) 0x00, (byte) 0x2A, (byte) 0x9E, (byte) 0x9A, (byte) 0x51,
        (byte) 0x30, (byte) 0x4F,
        (byte) 0x30, (byte) 0x0B,
        // id-sha512: 30 0b 06 09 60 86 48 01 65 03 04 02 03
        (byte) 0x06, (byte) 0x09, (byte) 0x60, (byte) 0x86, (byte) 0x48, (byte) 0x01, (byte) 0x65, (byte) 0x03, (byte) 0x04, (byte) 0x02, (byte) 0x03,
        // tl-v
        (byte) 0x04, (byte) 0x40};//0x40=512/8
    
    static Certificate  certificate     = null;
    static byte[]       signatureValue  = null;
    
    static String alg = "SHA512";
    static String msg = "Hello eDNI...";
    
    static String kId = "01";   // Key index
    static String cId = null;   // credential index
    static String pin = null;   // credential's pin
    
    static int  reader  = 3;    // reader index
    /**
     * to save certificates into files.cer
     */
    static String rootCer = "D:/Data/Projects/certificates/";
    static String authCer = "auth3401.cer";
    static String signCer = "sign3402.cer";
    
    /**
     * to connect to smart card and establish communication
     */
    Card            card;
    CardChannel     channel;
    ResponseAPDU    answer;
    
    private void initSC() throws CardException{
        //
        // Display the list of terminals
        TerminalFactory factory = TerminalFactory.getDefault();
        List<CardTerminal> terminals = factory.terminals().list();
        System.out.println("Terminals: " + terminals);
        
        // Use the first terminal
        CardTerminal terminal = terminals.get(reader);
        
        // Connect with the card
        card = terminal.connect("*");
        System.out.println("card: " + card);
        channel = card.getBasicChannel();
        
        // Select the pki app
        String aid = "A0000000770100700A1000F100000100";
        byte[] apSELECT_AUTHENTIC = concatenateTwoByteArray(select_aid, hexStringToByteArray(aid));
        System.out.println("select aid  : " + fromBytesToString(apSELECT_AUTHENTIC));
        answer = channel.transmit(new CommandAPDU(apSELECT_AUTHENTIC));
        System.out.println("answer 0 : " + answer.toString());
        System.out.println("data   0 : " + fromBytesToString(answer.getData()));
    }
    
    private void signHash() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, CardException, UnsupportedEncodingException, NoSuchProviderException  {
        // MSE
        byte[] MSE = concatenateTwoByteArray(mse, hexStringToByteArray(kId));
        System.out.println("apdu    mse : " + fromBytesToString(MSE));
        
        answer = channel.transmit(new CommandAPDU(MSE));
        System.out.println("answer  mse : " + answer.toString());
        /* NB
        if (kId.equals("01"))                       {cId = "01";  pin = "31313131FFFFFFFF";}    // auth pin
        if (kId.equals("02") || kId.equals("03"))   {cId = "04";  pin = "32323232FFFFFFFF";}    // sign pin
        //        */
//        /* RM
        if (kId.equals("01"))                       {cId = "01";  pin = "303530343738FFFF";}    // auth pin
        if (kId.equals("02") || kId.equals("03"))   {cId = "04";  pin = "303530343738FFFF";}    // sign pin
//        */
        String passphrase = cId + "08" + pin;
        byte[] VERIFY_PASSPHRASE = concatenateTwoByteArray(verify, hexStringToByteArray(passphrase));
        
        System.out.println("verify_passphrase : " + fromBytesToString(VERIFY_PASSPHRASE));
        answer = channel.transmit(new CommandAPDU(VERIFY_PASSPHRASE));
        System.out.println("answer verify pw  : " + answer.toString());
        
        
        // PSO
        byte[] PSO = null;
        if (alg.equals("SHA512"))  PSO = concatenateTwoByteArray(pso_sha512, calculateHash(alg, msg));
        if (alg.equals("SHA384"))  PSO = concatenateTwoByteArray(pso_sha384, calculateHash(alg, msg));
        if (alg.equals("SHA256"))  PSO = concatenateTwoByteArray(pso_sha256, calculateHash(alg, msg));
        if (alg.equals("SHA224"))  PSO = concatenateTwoByteArray(pso_sha224, calculateHash(alg, msg));
        if (alg.equals("SHA1"))    PSO = concatenateTwoByteArray(pso_sha1, calculateHash(alg, msg));
        System.out.println("apdu    pso : " + alg + " - " + fromBytesToString(PSO));
        
        answer = channel.transmit(new CommandAPDU(PSO));
        System.out.println("answer  pso : " + answer.toString());
        signatureValue = answer.getData();
        System.out.println("data    pso : " + signatureValue.length + " - " + alg + " - " + fromBytesToString(signatureValue));
        
        // Disconnect the card
        card.disconnect(false);
    }
    
    private void readSmartCard() throws Exception {
        //
        try {
            byte[] select_ef;
            //select 3F00
            String ef = "3F00";
            select_ef = concatenateTwoByteArray(select, hexStringToByteArray(ef));
            System.out.println("select MF : " + fromBytesToString(select_ef));
            answer = channel.transmit(new CommandAPDU(select_ef));
            if (answer.getSW() != 0x9000) {
                throw new Exception("No se logro seleccionar MF " + ef);
            }
            System.out.println("answer 1 : " + answer.toString());
            byte[] fciMF = answer.getData();
            System.out.println("data   1 : " + fromBytesToString(fciMF));
            
            //select 5015
            ef = "5015";
            select_ef = concatenateTwoByteArray(select, hexStringToByteArray(ef));
            System.out.println("select DF_5015 : " + fromBytesToString(select_ef));
            answer = channel.transmit(new CommandAPDU(select_ef));
            if (answer.getSW() != 0x9000) {
                throw new Exception("No se logro seleccionar EF " + ef);
            }
            System.out.println("answer 2: " + answer.toString());
            byte[] dedicatedFile = answer.getData();
            System.out.println("data   2 : " + fromBytesToString(dedicatedFile));
            
            int lengthDEC;  // tamanio del certificado
            
            // select EF - 340X
            ef = "34" + kId;
            select_ef = concatenateTwoByteArray(select, hexStringToByteArray(ef));
            System.out.println("apdu select EF : " + fromBytesToString(select_ef));
            answer = channel.transmit(new CommandAPDU(select_ef));
            byte[] tlvEf;// = answer.getData();
            if (answer.getSW() == 0x9000) {
//                System.out.println("TLV : " + fromBytesToString(tlvEf));
                tlvEf = answer.getData();
                // leer Tag 80 del TLV
                byte[] length3401Hex = new byte[2]; // DEC 1901, RM
                System.arraycopy(tlvEf, 4, length3401Hex, 0, 2);
                System.out.println("length (HEX): " + fromBytesToString(length3401Hex) + ", length (DEC): " + Integer.parseInt(fromBytesToString(length3401Hex), 16));
                lengthDEC = Integer.parseInt(fromBytesToString(length3401Hex), 16);
                
            } else {
                throw new Exception("No se logro seleccionar EF");
            }
            System.out.println("answer 3 : " + answer.toString());
            System.out.println("data   3 : " + fromBytesToString(tlvEf));
            
            // leer certificado 340X
            // la lectura necesariamente se realiza a travez de bucles, ya que el APDU max. devolvera 256 bytes...
            
//            printBinFormat(readRegistersFromSmartCard(lengthDEC, channel), rootCer + "bin" + ef + ".cer");
//            printPemFormat(fromByteArrayToX509Certificate(readRegistersFromSmartCard(lengthDEC, channel)), rootCer + "pem" + ef + ".cer");
            certificate = fromByteArrayToX509Certificate(readRegistersFromSmartCard(lengthDEC, channel));
//            printPEMFormat(certificate);
            
            // Disconnect the card
//            card.disconnect(false);
        } catch (Exception e) {
            System.out.println("Ouch: " + e.toString());
        }
    }
    
    private void readSC() throws CardException
    {
//        try
//        {
        byte[] select_ef = {(byte) 0x00, (byte) 0xA4, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x50, (byte) 0x15};//, (byte) 0x50, (byte) 0x15};
        System.out.println("select apdu : " + fromBytesToString(select_ef));
//            byte[] ef = {(byte)0x3F, (byte)0x00};
        byte[] ef = {(byte)0x50, (byte)0x15};
//            byte[] ef = {(byte)0x3F, (byte)0x00, (byte)0x50, (byte)0x15};
//            String ef = "3F0050153401";
        // { (byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02};
//            answer = channel.transmit(new CommandAPDU(select_ef));
        answer = channel.transmit(new CommandAPDU((byte)0x00, (byte)0xA4, (byte)0x00, (byte)0x0C, ef));
        if (answer.getSW() != 0x9000) {
            System.out.println("No se logro seleccionar ef ");
        }
        System.out.println("answer  : " + answer.toString());
        byte[] fciMF = answer.getData();
        System.out.println("data    : " + fromBytesToString(fciMF));
        
        // 002241B606800111830102
        byte[] mse = {(byte)0x80, (byte)0x01, (byte)0x11, (byte)0x83, (byte)0x01, (byte)0x02};
        answer = channel.transmit(new CommandAPDU((byte)0x00, (byte)0x22, (byte)0x41, (byte)0xB6, mse));
        if (answer.getSW() != 0x9000) {
            System.out.println("No se logro establecer el mse");
        }
        System.out.println("answer  : " + answer.toString());
        byte[] fciMSE = answer.getData();
        System.out.println("mes data    : " + fromBytesToString(fciMSE));
//        }
//        catch(Exception e)
//        {
//            System.out.println("error !!! --- " + e.getMessage());
//        }
    }
    
    public static void main(String[] args) throws Exception, NullPointerException, SignatureException {
        
        Security.addProvider(new BouncyCastleProvider());
        
        // TODO code application logic here
        try
        {
            SmartCardJava sc = new SmartCardJava();
            sc.initSC();
//            sc.readSC();
            sc.readSmartCard();
//            /*
            sc.signHash();
            sc.verify();
//            */
        }
        catch(CardException ce)
        {
            System.out.println("CardException : " + ce.getMessage());
        }
        catch(NullPointerException npe)
        {
            System.out.println("NullPointerCardException : " + npe.getMessage());
        }
//        catch(SignatureException se)
//        {
//            System.out.println("SignatureException : " + se.getMessage());
//        }
//            */
    }
    
    private void verify() throws NoSuchProviderException, UnrecoverableKeyException, CertificateException, IOException, KeyStoreException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        System.out.println("verify...");
        Signature sig = Signature.getInstance(alg + "withRSA", "BC");
        sig.initVerify(certificate.getPublicKey()); // certificate is got by readSmartCard() function
        sig.update(msg.getBytes());
        
        boolean valid = sig.verify(signatureValue); // signatureValue is got by sign() function
        System.out.println("verify : " + valid);
    }
    
    private byte[] readRegistersFromSmartCard(int length, CardChannel channel) throws CardException, Exception {
        byte[] register = new byte[length];
        int szReg = 256;
        int numBucles = length / szReg;
        byte numBytesRes = (byte) 0x00;
        byte[] readApdu;
        for (int i = 0; i <= numBucles; i++) {
            if (i == numBucles) {
                numBytesRes = (byte) (length % szReg);
            }
            readApdu = new byte[]{(byte) 0x00, (byte) 0xB0, (byte) i, (byte) 0x00, (byte) numBytesRes};
//            System.out.println("readApdu # " + i + " : " + fromBytesToString(readApdu));
            answer = channel.transmit(new CommandAPDU(readApdu));
            if (answer.getSW() == 0x9000) {
                if (numBytesRes == 0x00) {
                    System.arraycopy(answer.getData(), 0, register, szReg * i, szReg);
                } else {
                    System.arraycopy(answer.getData(), 0, register, szReg * i, (length % szReg));
                }
            } else {
                throw new Exception("No se logró completar la lectura del registro en la vuelta " + numBucles);
            }
//            System.out.println("register # " + i + " : " + fromByteArrayToHexString(register));
        }
        return register;
    }
}